﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input.Manipulations;
using System.Windows.Media;
using System.Windows.Media.Animation;
using ZOIL.Framework.Core;

namespace ZOIL.Framework.Presentation
{
    /// <summary>
    /// Provides a base class for all ZUI-context-aware controls.
    /// </summary>
    public class ZComponent : ContentControl
    {
        #region static members

        public static bool IsComponentInteractionEnabled = true;

        /// <summary>
        /// 
        /// </summary>
        public static int ZIndexCounter = 200;

        /// <summary>
        /// 
        /// </summary>
        public static int MaxZIndex = 200;

        #endregion

        #region private members

        private ISemanticZoom _semanticZoom;

        private bool _isOffScreen;

        private CacheMode _swapCacheMode;

        #endregion

        #region protected properties

        #region LastVisibility

        private Visibility _lastVisibility;

        /// <summary>
        /// 
        /// </summary>
        protected Visibility LastVisibility
        {
            get
            {
                return _lastVisibility;
            }
            set
            {
                _lastVisibility = value;
            }
        }

        #endregion

        #endregion

        #region Public Properties

        #region IsBeingManipulated

        public bool IsBeingManipulated
        {
            get
            {
                return (bool)GetValue(IsBeingManipulatedProperty);
            }
            set
            {
                SetValue(IsBeingManipulatedProperty, value);
            }
        }

        // Using a DependencyProperty as the backing store for IsBeingManipulated.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsBeingManipulatedProperty =
            DependencyProperty.RegisterAttached("IsBeingManipulated", typeof(bool), typeof(ZComponent), new UIPropertyMetadata(false));

        #endregion

        #region IsManipulatable

        public bool IsManipulatable
        {
            get { return (bool)GetValue(IsManipulatableProperty); }
            set
            {
                SetValue(IsManipulatableProperty, value);

            }
        }
        public static readonly DependencyProperty IsManipulatableProperty = DependencyProperty.Register("IsManipulatable", typeof(bool), typeof(ZComponent), new UIPropertyMetadata(true, OnIsManipulatableChanged, OnCoerceIsManipulatable));

        static object OnCoerceIsManipulatable(DependencyObject sender, object value)
        {
            return (bool)value;

        }

        static void OnIsManipulatableChanged(object sender, DependencyPropertyChangedEventArgs args)
        {


        }
        #endregion

        #region Angle

        /// <summary>
        /// 
        /// </summary>
        public double Angle
        {
            get { return (double)GetValue(AngleProperty); }
            set { SetValue(AngleProperty, value); }
        }

        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty AngleProperty = DependencyProperty.Register("Angle", typeof(double), typeof(ZComponent), new UIPropertyMetadata(0.0, OnAngleChanged, OnCoerceAngle));

        static object OnCoerceAngle(DependencyObject sender, object value)
        {
            double angle = (double)value;
            if (angle < 0)
            {
                angle = 360 + angle;
            }
            return angle;
        }

        static void OnAngleChanged(object sender, DependencyPropertyChangedEventArgs args)
        {
            ZComponent comp = sender as ZComponent;
            if (comp != null)
            {
                RotateTransform rt = comp.RenderTransform as RotateTransform;
                if (rt == null)
                {
                    rt = new RotateTransform();
                    comp.RenderTransform = rt;
                }

                double newAngle = (double)args.NewValue;
                rt.Angle = newAngle;
            }
        }

        #endregion

        #region InertiaProcessor

        public InertiaProcessor2D InertiaProcessor { get; set; }

        #endregion

        #region ManipulationProcessor

        public ManipulationProcessor2D ManipulationProcessor { get; set; }

        #endregion

        #region Physics Enabled

        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty PhysicsEnabledProperty =
            DependencyProperty.Register("PhysicsEnabled", typeof(Boolean), typeof(ZComponent), new PropertyMetadata(true));

        /// <summary>
        /// 
        /// </summary>
        public bool PhysicsEnabled
        {
            get { return (bool)GetValue(PhysicsEnabledProperty); }
            set { SetValue(PhysicsEnabledProperty, value); }
        }

        #endregion

        #region CurrentRepresentation

        /// <summary>
        /// When using semantic zoom, this returns the current visual tree.
        /// Otherwise, the element itself is returned.
        /// </summary>
        public FrameworkElement CurrentRepresentation
        {
            get
            {
                if (_semanticZoom != null && _semanticZoom.CurrentRepresentation != null)
                    return _semanticZoom.CurrentRepresentation;
                return this;
            }
        }

        #endregion

        #region IsOffscreen

        /// <summary>
        /// Indicates whether the component is completely outside of the current viewport.
        /// </summary>
        public bool IsOffScreen
        {
            get { return _isOffScreen; }
            protected set { _isOffScreen = value; }
        }

        #endregion

        #endregion

        #region dp

        #region ParentInformationLandscape

        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty ParentInformationLandscapeProperty = DependencyProperty.Register("ParentInformationLandscape", typeof(ZInformationLandscape), typeof(ZComponent));

        /// <summary>
        /// Returns the information landscape that hosts this ZComponent.
        /// Performance note: this works without an element tree look-up.
        /// </summary>
        public ZInformationLandscape ParentInformationLandscape
        {
            get { return (ZInformationLandscape)GetValue(ParentInformationLandscapeProperty); }
        }

        #endregion

        #region RenderedSize

        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty RenderedSizeProperty = DependencyProperty.Register("RenderedSize", typeof(Size), typeof(ZComponent), new FrameworkPropertyMetadata(OnSizeChanged));

        /// <summary>
        /// Returns the actual rendered size of the component.
        /// Contrary to WPF's FrameworkElement.RenderSize this one actually works.
        /// </summary>
        public Size RenderedSize
        {
            get { return (Size)GetValue(RenderedSizeProperty); }
        }

        private static void OnSizeChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            var component = sender as ZComponent;
            if (component != null) component.UpdateRepresentation();
        }

        #endregion

        #region ViewportIntersection

        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty ViewportIntersectionProperty = DependencyProperty.Register("ViewportIntersection", typeof(IntersectionDetail), typeof(ZComponent));

        /// <summary>
        /// Returns the intersection detail of the ZComponent's bounding region with the viewport.
        /// </summary>
        public IntersectionDetail ViewportIntersection
        {
            get { return (IntersectionDetail)GetValue(ViewportIntersectionProperty); }
        }

        #endregion

        #region LandscapeSizePercentage

        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty LandscapeSizePercentageProperty = DependencyProperty.Register("LandscapeSizePercentage", typeof(Size), typeof(ZComponent));

        /// <summary>
        /// The width and height percentage the ZComponent takes up of its parent information landscape.
        /// </summary>
        public Size LandscapeSizePercentage
        {
            get { return (Size)GetValue(LandscapeSizePercentageProperty); }
        }

        #endregion

        #region WindowSizePercentage

        /// <summary>
        /// 
        /// </summary>
        public static readonly DependencyProperty WindowSizePercentageProperty = DependencyProperty.Register("WindowSizePercentage", typeof(Size), typeof(ZComponent));

        /// <summary>
        /// The width and height percentage the ZComponent takes up of the client region of the window it is contained in.
        /// </summary>
        public Size WindowSizePercentage
        {
            get { return (Size)GetValue(WindowSizePercentageProperty); }
        }

        #endregion

        #endregion

        #region ctor

        /// <summary>
        /// 
        /// </summary>
        public ZComponent()
        {
            if (Background == null)
                // Make component clickable
                Background = new SolidColorBrush(Color.FromArgb(0, 0, 0, 0));

            InertiaProcessor = new InertiaProcessor2D();
            RenderTransform = new MatrixTransform();
            RenderTransformOrigin = new Point(0.5, 0.5);

            Loaded += OnLoaded;
        }

        #endregion

        #region event handling

        void OnLoaded(object sender, RoutedEventArgs e)
        {
            MaxZIndex = Math.Max(Panel.GetZIndex(this), MaxZIndex);
        }

        #endregion

        #region public methods

        public virtual void ZoomStarted()
        {
        }

        public virtual void ZoomFinished()
        {
        }

        public virtual void SwapCacheMode()
        {
            if (CacheMode == null)
            {
                CacheMode = _swapCacheMode ?? new BitmapCache(2.0);
                _swapCacheMode = null;
            }
            else
            {
                _swapCacheMode = CacheMode;
                CacheMode = null;
            }
        }

        /// <summary>
        /// rotates the zcomponent back to zero degrees
        /// </summary>
        public void AnimateToZeroAngle()
        {
            const int duration = 0;

            var angleAnimation = new DoubleAnimation
                                     {
                                         To = 0.0,
                                         Duration = new Duration(new TimeSpan(0, 0, 0, 0, duration)),
                                         AutoReverse = false
                                     };
            BeginAnimation(AngleProperty, angleAnimation);
        }

        /// <summary>
        /// 
        /// </summary>
        public void UpdateVisibility()
        {
#if DEBUG
            ZDebug.Log(DebugLevel.Spam, DebugCategories.Components, String.Format("{0}: {1:F0}px - Viewport intersection: {2}", Name, RenderedSize.Width, ViewportIntersection));
#endif
            // Hide the component if it is offscreen or below a certain
            // visibility threshold.
            if (!IsOffScreen && ViewportIntersection == IntersectionDetail.Empty)
            {
                IsOffScreen = !IsOffScreen;

                if (ZConfig.PruneOffScreenComponents)
                {
                    LastVisibility = Visibility;
                    Visibility = Visibility.Collapsed;
                }

#if DEBUG
                ZDebug.Log(DebugLevel.Spam, DebugCategories.Components, String.Format("{0} is off screen.", Name));
#endif
            }
            // Show components if they were offscreen/too small, but became
            // visible again.
            else if (IsOffScreen && ViewportIntersection != IntersectionDetail.Empty)
            {
                IsOffScreen = !IsOffScreen;

                if (ZConfig.PruneOffScreenComponents)
                    Visibility = LastVisibility;

#if DEBUG
                ZDebug.Log(DebugLevel.Spam, DebugCategories.Components, String.Format(
                    "{0} is displayed.", Name));
#endif
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public virtual void UpdateRepresentation()
        {
            UpdateVisibility();

            if (_semanticZoom == null)
                _semanticZoom = TryFindSemanticZoom(this);

            // Update semantic representation.
            if (_semanticZoom != null && !IsOffScreen)
                _semanticZoom.Update(this);
        }

        private static ISemanticZoom TryFindSemanticZoom(DependencyObject obj)
        {
            for (var i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
            {
                var child = VisualTreeHelper.GetChild(obj, i);
                var semanticZoom = child as ISemanticZoom;
                if (semanticZoom != null)
                    return semanticZoom;

                var childOfChild = TryFindSemanticZoom(child);
                if (childOfChild != null)
                    return childOfChild;
            }
            return null;
        }

        #endregion

        #region overrides

        /// <summary>
        /// 
        /// </summary>
        /// <param name="hitTestParameters"></param>
        /// <returns></returns>
        protected override GeometryHitTestResult HitTestCore(GeometryHitTestParameters hitTestParameters)
        {
            if (hitTestParameters == null)
            {
                throw new ArgumentNullException("hitTestParameters");
            }

            var geometry = new RectangleGeometry(VisualTreeHelper.GetDescendantBounds(this));
            return new GeometryHitTestResult(this, geometry.FillContainsWithDetail(hitTestParameters.HitGeometry));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="hitTestParameters"></param>
        /// <returns></returns>
        protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
        {
            if (hitTestParameters == null)
            {
                throw new ArgumentNullException("hitTestParameters");
            }

            if (VisualTreeHelper.GetDescendantBounds(this).Contains(hitTestParameters.HitPoint))
            {
                return new PointHitTestResult(this, hitTestParameters.HitPoint);
            }

            return null;
        }

        #endregion
    }
}
